/*
 * @Author: Betty
 * @Date: 2020-11-27 22:52:35
 * @LastEditors: Betty
 * @LastEditTime: 2021-03-21 18:12:49
 * @Description: vuex的使用
 */
import { createStore, Commit } from 'vuex'
// 先从全局数据里拿到专栏和文章的测试数据（现在不拿了，改成去服务器获取数据）
// 发起请求要使用axios，所以引入它
import axios, { AxiosRequestConfig } from 'axios'
import { arrToObject, objectToArr } from './helper'
// 定义用户类型
// 注意根据服务器返回的数据修改属性名字和类型，如id改为_id，类型改为string类型，columnId改为column,name改为nickName
// 因为需要展示用户头像和简介，所以加上这两个属性
export interface UserProps {
    isLogin: boolean;
    nickName?: string;
    _id?: string;
    column?: string;
    emain?: string;
    avatar?: ImageProps;
    discription?: string;
}
// 定义一个通用的响应类型
export interface ResponseType<P = {}> {
    code: number;
    message: string;
    data: P;
}
// 定义一个表达文章和专栏等类似类型的列表，具体是哪种类型的列表，使用泛型
interface ListProps<P> {
    // 用数据的_id作为key，值为数据
    [id: string]: P;
}
// 定义通用错误类型
export interface GlobalErrorProps {
    status: boolean;
    message?: string;
}
// 因为图片现在不只是单独的url了，而是一个对象，所以我们来定义一下图片类型
export interface ImageProps {
    fitUrl?: string;
    _id?: string;
    url?: string;
    createAt?: string;
}
// 定义专栏类型
export interface ColumnProps {
    _id: string;
    title: string;
    avatar?: ImageProps;
    description: string;
}
// 定义文章类型
export interface PostProps {
    isHTML?: boolean;
    _id?: string;
    title: string;
    // 这个是摘要，是内容中的一小段
    excerpt?: string;
    content?: string;
    // 因为上传图片的时候，图片是以字符串形式存在，所也可能是字符串
    image?: ImageProps | string;
    createdAt?: string;
    column: string;
    // 加上作者的属性
    author?: string | UserProps;
}
// 定义一个全局对象类型，里面有我们要用的数据，注意专栏和文章是数组
// 记得导出这个类型备用
export interface GlobalDataProps {
    error: GlobalErrorProps;
    loading: boolean;
    token: string;
    user: UserProps;
    // 给columns和posts外面再套一层，给它们加上total属性，类型为number，表示共有多少数据
    // 专栏这边，把“isLoaded”改为“当前加载到第几页”，因为是否要加载，只要看当前是否最后一页
    columns: { data: ListProps<ColumnProps>; currentPage: number; total: number;};
    // 文章这边，要把“已加载专栏”改为比较复杂的对象
    posts: { data: ListProps<PostProps>; loadedColumns: string[];};
    
}
// 由于每次请求都需要get数据，commit触发mutation，所以我们可以把这些操作封装到函数里
// 参数有请求的url，要触发的mutation的名字，commit（commit的类型就是Commit）
// const getAndCommit = async (url: string, mutationName: string, commit: Commit) => {
//     // 设置loading的操作已经移到拦截器中了
//     // 在发送请求前，我们设置一下loading的状态，设置为true，表示开始处于加载状态了
//     // 使用axios，发起get请求，返回一个promise
//     // 在这里，我们用context对象调用commit方法，提交到对应的mutation，
//     // 要传入的参数就是本次获取的结果
//     const { data } = await axios.get(url)
//     commit(mutationName, data)
//     // 在请求完成后，我们再次设置loading，让它为false，表示加载完毕
//     //把结果返回出去
//     return data
// }
// 创建一个post请求，之后唤醒某个mutation的函数
// 参数仍然有要请求的url,要唤醒的mutation名字，commit，多了一个payload，要提交的参数
// const postAndCommit = async (url: string, mutationName: string, commit: Commit, payload: any) => {
//     const { data } = await axios.post(url, payload)
//     commit(mutationName, data)
//     return data
// }
// 来准备一个通用的方法，涵盖这些请求方式，里面的都差不多，就多一个指定请求方式和参数的配置对象而已
// 新增一个可选参数，传递额外的信息
const asyncAndCommit = async (url: string, mutationName: string, 
    commit: Commit, config: AxiosRequestConfig = { method: 'get' },
    extraData?: any) => {
    // 这里不指定用axios的哪个方法，而是直接用axios函数
    const { data } = await axios(url, config)
    // 假如我确实有传入额外的数据，那么我把额外数据与原始数据封装成对象，传给commit
    if (extraData) {
        commit(mutationName, {
            data,
            extraData
        })
    } else {
        commit(mutationName, data)
    }
    return data
}
// 创建store，createStore方法接受一个泛型，这里的泛型是我们定义自定义类型
const store = createStore<GlobalDataProps>({
    // 在里面的state中定义这个项目所需的共享的数据，包括用户User,专栏ColumnProps，文章PostProps
    state: {
        error: {
            status: false
        },
        loading: false,
        columns: { data: {}, currentPage: 0, total: 0 },
        posts: { data: {}, loadedColumns: [] },
        token: localStorage.getItem('token') || '',
        // 默认情况下没有登录
        user: {
            isLogin: false
        }
    },
    // 定义修改state的方法
    mutations: {
        // 这个是测试用的mutation
        // 第一个参数就是state
        // 假设登录成功后，修改user的状态，设置用户名，并设置已登录
        // login(state) {
        //     state.user = { ...state.user, isLogin: true, name: 'Betty' }
        // },
        // 新建文章的方法，第二个参数用来接收传入的参数
        createPost(state, newPost) {
            // 把新文章的数据push到文章数组中
            // state.posts.push(newPost)
            // 因为它不是对象了，所以赋值的方法改成：给它新增一个属性，把值作为属性值
            state.posts.data[newPost._id] = newPost
        },
        // 删除文章的mutation
        deletePost(state, { data }) {
            // 实际上就是对文章数组进行过滤，id与data不同的留下来
            // state.posts = state.posts.filter(post => post._id !== data._id)
            // 改成对象以后更简单了，只要删除 属性名 为data的_id的属性即可
            delete state.posts.data[data._id]
        },
        // 定义获取专栏后赋值的mutation
        fetchColumns(state, rawData) {
            // 从数据中取出数组，赋值给专栏数据
            // 因为我们要操作的是state.columns和rawData.data里的数据，所以把它们解构一下
            const { data } = state.columns
            const { list, count, currentPage } = rawData.data
            // 把准备好的值再赋给state.columns，
            state.columns = {
                // data里面不仅包括之前的data，还要有现有结果的展开
                data: {
                    ...data,
                    // 现在是要把数组的值赋给对象类型，所以需要做一个转换
                    ...arrToObject(list)
                },
                // 记录一下总数
                total: count,
                // 拿到专栏列表后，标记一下专栏列表已经拿过了
                currentPage: currentPage * 1
            }
            console.log('输出专栏有多少个:',state.columns.total)
        },
        // 定义根据id获取专栏信息后赋值的mutation
        fetchColumn(state, rawData) {
            // 把我根据id拿到的专栏数据作为专栏数组里的唯一一个元素
            // state.columns = [rawData.data]
            // 把返回的数据作为属性加到columns对象上
            state.columns.data[rawData.data._id] = rawData.data
        },
        // 定义获取文章列表的赋值mutation
        fetchPosts(state, { data: rawData, extraData: columnId }) {
            // 像刚才一样，把list和count解构出来，还有state.posts.data
            const { list } = rawData
            const { data, loadedColumns } = state.posts
            // 把现在的这个专栏id加到“已加载专栏”数组去
            loadedColumns.push(columnId)
            // 把整理好的值赋给state.posts。data不仅包括了之前的data，还有刚刚拿到手的list数据
            state.posts = {
                data: {
                    ...data,
                    ...arrToObject(list)
                },
                loadedColumns
            }
        },
        // 定义根据id来获取文章信息后赋值的mutation
        fetchPost(state, rawData) {
            // state.posts = [rawData.data]
            // 把数据加到属性里面来
            state.posts.data[rawData.data._id] = rawData.data
        },
        // 定义用来修改loading的mutation
        setLoading(state, status) {
            state.loading = status
        },
        // 登录后进行赋值的mutation
        login(state, rawData) {
            const { token } = rawData.data
            // token不仅赋值给state，也要让axios设置到首部字段Author...中
            state.token = token
            // token要想持久化存储，就要存到localStorge中
            localStorage.setItem('token', token)
            // 登录成功后，就把token写到请求报文首部的Authorization字段里面
            axios.defaults.headers.common.Authorization = `Bearer ${token}`
        },
        // 获取到当前用户信息的赋值操作
        fetchCurrentUser(state, rawData) {
            // 把得到的用户信息赋值给state的user属性
            state.user = {
                isLogin: true,
                ...rawData.data
            }
            console.log(rawData.data)
        },
        // 设置error的mutation
        setError(state, e: GlobalErrorProps) {
            state.error = e
        },
        // 退出登录的mutation
        logout(state) {
            // 清空store里的token
            state.token = ''
            // 设置isLogin为false
            state.user.isLogin = false
            // 把请求头的Authorization字段删掉
            delete axios.defaults.headers.Authorization
            // 删除localstorage的token
            localStorage.removeItem('token')
        },
        // 更新文章的mutation
        updatePost(state, { data }) {
            // 在state里的文章数组中找到当前的文章，进行覆盖
            // state.posts = state.posts.map(post => {
            //     if (post._id === data._id) {
            //         return data
            //     } else {
            //         return post
            //     }
            // })
            // 改成对象以后更简单了，只要把指定的属性的值更新就好
            state.posts.data[data._id] = data
        }
    },
    // getter相当于store里的计算属性，结果也是被缓存的
    getters: {
        // 定义一个获取专栏列表的getter，只需要把state里的专栏数据返回即可
        getColumns: state => {
            // 这里没有参数，只要直接返回结果就好，不需要套娃
            // 我们要用的是数组，所以返回数组类型的结果
            return objectToArr(state.columns.data)
        },
        // 定义一个根据id获取对应专栏信息的getter
        // 返回的也是一个箭头函数，这个箭头函数来接受参数，并返回最后的计算结果
        getColumnById: state => (id: string) => {
            // return state.columns.find(c => c._id === id)
            // 改成对象以后更简单了，只要返回当前id指定的属性就可以
            return state.columns.data[id]
        },
        // 定义一个根据专栏id，获取对应文章的getter
        getPostsByCid: state => (cid: string) => {
            // 这里是要挑出某专栏的所有文章，肯定是需要过滤
            // 因为我们是对象了，需要过滤就只能转成数组再过滤
            return objectToArr(state.posts.data).filter(post => post.column == cid)
        },
        // 定义一个根据文章id，获取文章信息的getter
        getPostByPid: state => (id: string) => {
            // return state.posts.find(post => post._id === id)
            // 也是直接返回指定的属性值就可以了
            return state.posts.data[id]
        }
    },
    // 到这里定义action，它可以发起异步请求
    actions: {
        // 现在所有的获取操作只需要调用getAndCommit函数就好
        // 向服务器发起请求，获取专栏列表
        fetchColumns({ state, commit }, params = {}) {
            // 把请求的参数解构出来，默认当前页是第一页
            const { currentPage = 1, pageSize = 6} = params
            // 判断已经请求的最大页是否小于当前请求的页码，是就继续请求
            if (state.columns.currentPage < currentPage) {
                return asyncAndCommit(`/columns?currentPage=${currentPage}&pageSize=${pageSize}`, 'fetchColumns', commit)
            }
        },
        // 向服务器发起请求，获取专栏详情
        fetchColumn({ state, commit }, cid) {
            // 判断是否已加载过这个专栏，判断data里是不是已经有过这个数据了，没有才请求
            if (!state.columns.data[cid]) {
                return asyncAndCommit(`/columns/${cid}`, 'fetchColumn', commit)
            }
        },
        // 向服务器发起请求，获取文章列表
        // 给它加上一个参数，关于分页的参数
        fetchPosts({ commit }, {cid, params = {}}) {
            // if (!state.posts.loadedColumns.includes(cid)) {
            //     return asyncAndCommit(`/columns/${cid}/posts`, 'fetchPosts', commit, { method: 'get' }, cid)
            // }
            const { currentPage = 1,  pageSize = 5 } = params
            return asyncAndCommit(`/columns/${cid}/posts?currentPage=${currentPage}&pageSize=${pageSize}`, 'fetchPosts', commit, { method: 'get' }, cid)
        },
        // 根据id，获取文章信息
        fetchPost({ state, commit }, pid) {
            const currentPost = state.posts.data[pid]
            // 确定文章集合的数据里没有id为当前文章，才会请求去获取
            // 虽然我们从列表里拿到了文章数据，但是缺少了content，无法使用，所以要判断是否有content
            if (!currentPost || !currentPost.content) {
                return asyncAndCommit(`/posts/${pid}`, 'fetchPost', commit)
            } else {
                // 不发请求的时候，也返回一个结果的data属性为文章数据的promise
                return Promise.resolve({
                    data: currentPost
                })
            }
        },
        // 发送登录请求的action，第二个参数是payload，也就是表单里的信息
        login({ commit }, payload) {
            return asyncAndCommit(`/user/login`, 'login', commit, {
                method: 'post',
                data: payload
            })
        },
        // 写一个获取当前登录用户的信息的action
        fetchCurrentUser({ commit }) {
            return asyncAndCommit(`/user/current`, 'fetchCurrentUser', commit)
        },
        // 定义一个提交文章的action
        createPost({ commit }, payload) {
            return asyncAndCommit(`/posts`, 'createPost', commit, {
               method: 'post',
               data: payload
            })
        },
        // 删除文章的action
        deletePost({ commit }, id) {
            return asyncAndCommit(`/posts/${id}`, 'deletePost', commit, {
               method: 'delete'
            })
        },
        // 登录与获取当前用户信息一起来
        loginAndFetch({ dispatch }, loginData) {
            // 返回这个结果之前，先触发login的action，然后等login有了结果后
            // 再触发fetchCurrentUser的action去获取用户信息,记得要返回用户信息，因为我们要拿到页面上去用的
            return dispatch('login', loginData).then(() => {
                return dispatch('fetchCurrentUser')
            })
        },
        // 更新文章的action
        updatePost({ commit }, { id, payload } ) {
            return asyncAndCommit(`/posts/${id}`, 'updatePost', commit, {
                // 在config这里指定方法为patch，在data这里传入要提交的数据 
                method: 'patch',
                data: payload
            })
        }
    }
})
// 导出store对象
export default store